home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / foxclocks-2.2.23-fx+tb+sb.xpi / components / engine.js < prev    next >
Encoding:
Text File  |  2007-10-21  |  24.5 KB  |  637 lines

  1. // FoxClocks extension for Mozilla Firefox/Thunderbird/Sunbird
  2. // Copyright (C) 2005-2007 Andy McDonald / www.stemhaus.com
  3. // For licensing terms, please refer to readme.txt in this extension's '.xpi'
  4. // package or its installation directory on your computer.
  5.  
  6. // AFM - from http://forums.mozillazine.org/viewtopic.php?t=308369
  7.  
  8. // ====================================================================================
  9. const CI = Components.interfaces, CC = Components.classes, CR = Components.results;
  10.  
  11. // ====================================================================================
  12. FoxClocks_Engine.classID = Components.ID("88575671-c0d6-4802-8bd5-a12cfd36247a");
  13. FoxClocks_Engine.contractID = "@stemhaus.com/firefox/foxclocks/engine;1";
  14. FoxClocks_Engine.classDescription = "FoxClocks Engine";
  15.  
  16. // ====================================================================================
  17. // AFM - Event model:
  18. // - only the engine interfaces with preferences; specifically, other components
  19. //   do not receive pref change notifications
  20. // - engine calls methods on the other components directly; it does not broadcast
  21. //   events to other components
  22. // - engine communicates to chrome JS via ("foxclocks") broadcasts only (the only
  23. //   alternative is pref changes, which makes no sense)
  24. // - currently async notifications from other components back to the engine are by
  25. //   by broadcast. Not sure about this one, but certainly other components are not
  26. //   aware of the engine
  27. // - Note that this model is only enforced in components - chrome JS is still strongly
  28. //   coupled to pref change events, and to writing preferences. Todo, one day
  29. // ====================================================================================
  30.  
  31. // ====================================================================================
  32. function FoxClocks_Engine()
  33. {
  34.     this.componentStarted = false;
  35.  
  36.     this._logger = null;
  37.     this._prefManager = null;
  38.     this._updateManager = null;
  39.     this._utils = null;
  40.     this._zoneManager = null;
  41.     this._observerService = null;
  42.     
  43.     this._components = [];
  44. }
  45.  
  46. // ====================================================================================
  47. FoxClocks_Engine.prototype =
  48. {    
  49.     // ====================================================================================
  50.     startup: function()
  51.     {
  52.         this._logger = CC["@stemhaus.com/firefox/foxclocks/logger;1"].getService(CI.nsISupports).wrappedJSObject;
  53.          this._prefManager = CC["@stemhaus.com/firefox/foxclocks/prefmanager;1"].getService(CI.nsISupports).wrappedJSObject;
  54.           this._updateManager = CC["@stemhaus.com/firefox/foxclocks/updatemanager;1"].getService(CI.nsISupports).wrappedJSObject;
  55.            this._utils = CC["@stemhaus.com/firefox/foxclocks/utils;1"].getService(CI.nsISupports).wrappedJSObject;            
  56.           this._zoneManager = CC["@stemhaus.com/firefox/foxclocks/zonemanager;1"].getService(CI.nsISupports).wrappedJSObject;            
  57.            this._watchlistManager = CC["@stemhaus.com/firefox/foxclocks/watchlistmanager;1"].getService(CI.nsISupports).wrappedJSObject;            
  58.  
  59.         this._components = [this._logger, this._prefManager, this._updateManager, this._utils,
  60.                                 this._zoneManager, this._watchlistManager];
  61.  
  62.         this._observerService = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
  63.         
  64.         this._prefManager.declarePrefAsXml("foxclocks.watchlist");
  65.         
  66.         // AFM - so that the pref manager will set these values if imported from file
  67.         // (we don't need to declare foxclocks.zones.indexes.* as legacy since they
  68.         // were never exported to file. All a bit of a hack really
  69.         //
  70.         this._prefManager.declarePrefAsLegacy("foxclocks.zones");
  71.          this._prefManager.addPrefObserver("foxclocks.", this);
  72.         this._prefManager.addPrefObserver("extensions." + this._utils.FC_GUID_FOXCLOCKS + ".", this);
  73.         this._observerService.addObserver(this, "foxclocks", false);
  74.         
  75.         this._onComponentStartup();
  76.         
  77.         this.componentStarted = true;
  78.         CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService)
  79.             .notifyObservers(this, "foxclocks", "component:startup");
  80.     },
  81.         
  82.     // ====================================================================================
  83.     shutdown: function()
  84.     {            
  85.         this._prefManager.removePrefObserver("foxclocks.", this);
  86.         this._prefManager.removePrefObserver("extensions." + this._utils.FC_GUID_FOXCLOCKS + ".", this);
  87.         this._observerService.removeObserver(this, "foxclocks");
  88.     },
  89.     
  90.     // ====================================================================================
  91.     onObserve: function(aSubject, aTopic, aData)
  92.     {
  93.         // AFM - rather than observe(), which we leave as boilerplate below
  94.         //
  95.     
  96.         // this._logger.log("FoxClocks_Engine::onObserve(): <" + aTopic + ">, <" +
  97.         // aData + ">", this._logger.INFO);
  98.                 
  99.         if (aTopic == "foxclocks")
  100.         {                
  101.             if (aData == "component:startup")
  102.             {
  103.                 if (aSubject.wrappedJSObject != this)
  104.                     this._onComponentStartup();
  105.             }
  106.             else if (aData == "updatemanager:update-complete")
  107.             {
  108.                 this._onDbUpdateComplete();
  109.             }
  110.             else if (aData == "watchlistmanager:watchlist-changed")
  111.             {
  112.                 this._onWatchlistUpdated();
  113.             }
  114.             else if (aData == "prefmanager:legacy-prefs-imported")
  115.             {
  116.                 this._convertLegacyParams(aSubject.wrappedJSObject.getImportedLegacyPrefs());
  117.             }
  118.         }
  119.         else if (aTopic == "nsPref:changed")
  120.         {
  121.             switch (aData)
  122.             {
  123.                 case "foxclocks.logging.tmpfile.enabled": this._enableTmpLog(); break;
  124.                 case "foxclocks.logging.level": this._setLogLevel(); break;
  125.                 case "foxclocks.data.update.auto.enabled": this._initUpdateManager(); break;
  126.                 case "foxclocks.data.update.auto.interval": this._initUpdateManager(); break;
  127.                 case "foxclocks.data.update.noserver.updateurl": this._initUpdateManager(); break;
  128.                 case "foxclocks.watchlist": this._populateWatchlistFromPrefs(); break;
  129.                 
  130.                 case "extensions." + this._utils.FC_GUID_FOXCLOCKS + ".devel": this._initUpdateManager(); break;
  131.                 case "extensions." + this._utils.FC_GUID_FOXCLOCKS + ".data.update.prevupdate":
  132.                         this._initUpdateManager(); break;
  133.                         
  134.                 case "foxclocks.zonepicker.dataurl":
  135.                 {        
  136.                     if (this._initZonePicker() == true)
  137.                         this._observerService.notifyObservers(null, "foxclocks", "engine:zone-picker-changed");
  138.                     break;
  139.                 }    
  140.             }
  141.         }
  142.         else if (aTopic == "alertclickcallback")
  143.         {
  144.             if (aData == "engine-internal:foxclocks-zone-data-update-complete:new")
  145.                 this._utils.openFoxClocksDbUpdateURL();
  146.         }
  147.     },
  148.  
  149.     // ====================================================================================
  150.     _onComponentStartup: function()
  151.     {
  152.         // AFM - make sure that all components on which we depend have started up before we do anything
  153.         //
  154.         var startCount = 0;
  155.         
  156.         for (var i=0; i < this._components.length; i++)
  157.         {
  158.             if (this._components[i].componentStarted == true)
  159.                 startCount++;
  160.         }
  161.         
  162.         var allStarted = (startCount == this._components.length);
  163.         
  164.         // this._logger.log("FoxClocks_Engine::_onComponentStartup(): " + startCount + "/" +
  165.         //    this._components.length + " components started", this._logger.INFO);
  166.             
  167.         if (allStarted == true)
  168.             this._init();
  169.         
  170.         this._logger.log("-FoxClocks_Engine::_onComponentStartup()");
  171.     },
  172.     
  173.     // ====================================================================================
  174.     _init: function(noUpdateManager)
  175.     {
  176.         // AFM - the real initialisation, after we're sure all other components are up
  177.         // There's still stuff in foxclocksoverlay.js that isn't window-specific -
  178.         // should be moved here
  179.         //
  180.         this._setLogLevel(); // ASAP
  181.         this._enableTmpLog();    
  182.         
  183.         // AFM - local data url is available before update manager is initialised
  184.         //
  185.         this._zoneManager.initZoneData(this._updateManager.getLocalDataURL());
  186.         this._initZonePicker();
  187.         
  188.         // AFM - need above zone data to convert legacy params and populate watchlist
  189.         //
  190.         this._convertLegacyParams(null);
  191.         this._populateWatchlistFromPrefs();
  192.                 
  193.         if (noUpdateManager != true)
  194.             this._initUpdateManager();
  195.         
  196.         this._doDiagnostics();
  197.     },
  198.                     
  199.     // ====================================================================================
  200.     _initUpdateManager: function()
  201.     {
  202.         var autoUpenabled = this._prefManager.getPref("foxclocks.data.update.auto.enabled");
  203.         
  204.         // AFM - maximum one update a day
  205.         //
  206.         var prevUpdateSecs = this._prefManager.getPref("extensions." +
  207.                     this._utils.FC_GUID_FOXCLOCKS + ".data.update.prevupdate");
  208.                     
  209.         var isDevelParam = this._prefManager.getPref("extensions." + this._utils.FC_GUID_FOXCLOCKS + ".devel");
  210.         var isDevel = isDevelParam != null && isDevelParam == true;
  211.             
  212.         var autoUpdateIntervalParam = this._prefManager.getPref("foxclocks.data.update.auto.interval");
  213.         
  214.         // AFM - minimum of 1 day, if we're not in devel mode
  215.         //
  216.         var autoUpdateInterval = isDevel ? autoUpdateIntervalParam : Math.max(autoUpdateIntervalParam, 86400);
  217.         
  218.         var noServerUpdateURL = this._prefManager.getPref("foxclocks.data.update.noserver.updateurl");
  219.         if (noServerUpdateURL == "")
  220.             noServerUpdateURL = null;
  221.             
  222.         this._updateManager.init(autoUpenabled, autoUpdateInterval, prevUpdateSecs, isDevel, noServerUpdateURL);
  223.         
  224.         // AFM - the way the GUI is notified of these changes is poor; if the devel param is modified,
  225.         // eg, the GUI doesn't update (because it's not watching the extensions.{ branch
  226.         //
  227.     },
  228.                     
  229.     // ====================================================================================
  230.     _initZonePicker: function()
  231.     {
  232.         const BUILTIN_ZONE_PICKER_DATA_URL = "chrome://foxclocks/locale/zonepicker.xml";
  233.         
  234.         this._logger.log("+FoxClocks_Engine::_initZonePicker()");
  235.                         
  236.           var zonePickerDataUrl = this._prefManager.getPref("foxclocks.zonepicker.dataurl");
  237.         
  238.         if (zonePickerDataUrl == "fc-zonepicker-dataurl-builtin")
  239.             zonePickerDataUrl = BUILTIN_ZONE_PICKER_DATA_URL;
  240.     
  241.         // AFM - failure implies non-builtin URL for non-existent/non-readable file. Probably
  242.         //            
  243.         var ret = this._zoneManager.initZonePicker(zonePickerDataUrl);
  244.         if (ret == false && zonePickerDataUrl != BUILTIN_ZONE_PICKER_DATA_URL)
  245.         {
  246.             this._logger.log("FoxClocks_Engine::_initZonePicker(): failed to load zone picker locale data from "
  247.                 + zonePickerDataUrl  + " - using built-in locale data", this._logger.ERROR);
  248.                 
  249.             ret = this._zoneManager.initZonePicker(BUILTIN_ZONE_PICKER_DATA_URL);
  250.         }
  251.         
  252.         if (ret != true)
  253.             this._logger.log("FoxClocks_Engine::_initZonePicker(): failed to load built-in " +
  254.             "zone picker locale data. Giving up", this._logger.ERROR);
  255.             
  256.         this._logger.log("-FoxClocks_Engine::_initZonePicker()");            
  257.         return ret;
  258.     },
  259.     
  260.     // ====================================================================================        
  261.     _onDbUpdateComplete: function()
  262.     {
  263.         // AFM - we set the param on completion of any update attempt
  264.         //
  265.         this._prefManager.removePrefObserver("extensions." + this._utils.FC_GUID_FOXCLOCKS + ".", this);
  266.         this._prefManager.setPref("extensions." + this._utils.FC_GUID_FOXCLOCKS + ".data.update.prevupdate",
  267.                 Math.round(this._updateManager.lastUpdateDate.getTime()/1000));
  268.         this._prefManager.addPrefObserver("extensions." + this._utils.FC_GUID_FOXCLOCKS + ".", this);
  269.  
  270.         if (this._updateManager.lastUpdateResult == "OK_NEW")
  271.         {    
  272.             if (this._prefManager.getPref("foxclocks.data.update.auto.alert.enabled") == true)
  273.                 this._doDatabaseUpdateAlert();
  274.                                
  275.             this._logger.log("FoxClocks_Engine::_onDbUpdateComplete(): reinitialising on new data");
  276.             this._init(true); // do not re-init update manager
  277.         }
  278.             
  279.         this._observerService.notifyObservers(this._updateManager, "foxclocks", "engine:zone-data-update-complete");    
  280.     },
  281.     
  282.     // ====================================================================================
  283.     _onWatchlistUpdated: function()
  284.     {
  285.         this._prefManager.setPref("foxclocks.watchlist", this._watchlistManager.watchlistToXmlString());
  286.         this._observerService.notifyObservers(this._watchlistManager, "foxclocks", "engine:watchlist-changed");    
  287.     },
  288.     
  289.     // ====================================================================================
  290.     _populateWatchlistFromPrefs: function()
  291.     {
  292.         this._logger.log("+FoxClocks_Engine::_populateWatchlistFromPrefs()");
  293.         this._watchlistManager.watchlistFromXmlString(this._prefManager.getPref("foxclocks.watchlist"));
  294.         this._observerService.notifyObservers(this._watchlistManager, "foxclocks", "engine:watchlist-changed");
  295.         this._logger.log("-FoxClocks_Engine::_populateWatchlistFromPrefs()");
  296.     },
  297.     
  298.     // ====================================================================================
  299.     _doDatabaseUpdateAlert: function()
  300.     {
  301.          var stringBundleService = CC["@mozilla.org/intl/stringbundle;1"].getService(CI.nsIStringBundleService);
  302.         var foxClocksBundle = stringBundleService.createBundle("chrome://foxclocks/locale/foxclocks.properties");
  303.         
  304.         // AFM - reusing this property here
  305.         //
  306.         var alertText = foxClocksBundle.GetStringFromName("options.data.update.last.status.ok_new.label");
  307.         var alertsService = CC["@mozilla.org/alerts-service;1"].getService(CI.nsIAlertsService);
  308.         
  309.         // AFM - register 'this' to observe 'alertclickcallback' and 'alertfinished'
  310.         //
  311.         alertsService.showAlertNotification("chrome://foxclocks/skin/icon.png", 
  312.             "FoxClocks", alertText, true, "engine-internal:foxclocks-zone-data-update-complete:new", this);
  313.     },
  314.             
  315.     // ====================================================================================
  316.     _setLogLevel: function()
  317.     {
  318.         this._logger.setLevel(this._prefManager.getPref("foxclocks.logging.level"));
  319.     },
  320.  
  321.     // ====================================================================================
  322.     _enableTmpLog: function()
  323.     {
  324.         this._logger.enableTmpLog(this._prefManager.getPref("foxclocks.logging.tmpfile.enabled"));
  325.     },    
  326.     
  327.     // ====================================================================================
  328.     _convertLegacyParams : function(importedLegacyParams)
  329.     {
  330.         // AFM - we need to convert legacy params on startup and preference file import
  331.         //
  332.         this._logger.log("+FoxClocks_Engine::_convertLegacyParams()");
  333.  
  334.         var prefService = CC["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefBranch);
  335.         prefService.QueryInterface(CI.nsIPrefService);
  336.         
  337.         var watchlistItems = new Array();                            
  338.         this._convertLegacyZonesParam(watchlistItems, importedLegacyParams);
  339.         
  340.         if (watchlistItems.length > 0)
  341.         {
  342.             this._watchlistManager.setWatchlist(watchlistItems);
  343.             
  344.             // AFM - this call actually notifies the engine to update the pref...
  345.             // 
  346.             this._watchlistManager.setUpdated();
  347.         }
  348.         
  349.         // AFM - rename foxclocks.prevrun.version - params starting 'foxclocks.' now relate to true
  350.         // configuration, and can be imported/exported
  351.         //    
  352.         var oldPrevRunVersionParam = this._prefManager.getPref("foxclocks.prevrun.version");
  353.         if (oldPrevRunVersionParam != null)
  354.         {
  355.             this._prefManager.setPref("extensions." + this._utils.FC_GUID_FOXCLOCKS +
  356.                     ".prevrun.version", oldPrevRunVersionParam);
  357.         }
  358.         
  359.         // AFM - delete old prefs
  360.         //
  361.         prefService.getBranch("foxclocks.firstrun").deleteBranch("");
  362.         prefService.getBranch("foxclocks.updateinterval").deleteBranch("");
  363.         prefService.getBranch("foxclocks.zones").deleteBranch("");
  364.         prefService.getBranch("foxclocks.prevrun.version").deleteBranch("");
  365.  
  366.         this._logger.log("-FoxClocks_Engine::_convertLegacyParams()");
  367.     },
  368.  
  369.     // ====================================================================================
  370.     _convertLegacyZonesParam : function(watchlistItems, importedLegacyParams)
  371.     {
  372.         this._logger.log("+FoxClocks_Engine::_convertLegacyZonesParam()");
  373.  
  374.         // AFM - foxclocks.zones param to array of WatchlistItems
  375.         //
  376.                 
  377.         zonesPref = (importedLegacyParams != null) ? importedLegacyParams["foxclocks.zones"]:
  378.             this._prefManager.getPref("foxclocks.zones");
  379.             
  380.         if (zonesPref == null)
  381.         {
  382.             this._logger.log("-FoxClocks_Engine::_convertLegacyZonesParam(): no legacy data - nothing to do");
  383.             return false;
  384.         }
  385.         
  386.         const FC_LOCATION_DELIM = '|';
  387.         var zonesPrefArray = zonesPref.split(FC_LOCATION_DELIM);
  388.     
  389.         for (var i = 0; zonesPrefArray != "" && i < zonesPrefArray.length; i++)
  390.         {        
  391.             var watchlistItem = this._watchlistManager.createItem(null);
  392.             if (watchlistItem.legacyFromZoneParam(zonesPrefArray[i]) == false)
  393.                 continue;
  394.  
  395.             watchlistItems.push(watchlistItem);
  396.         }
  397.         
  398.         this._logger.log("-FoxClocks_Engine::_convertLegacyZonesParam()", this._logger.INFO);
  399.             
  400.         return true;
  401.     },
  402.     
  403.     // ====================================================================================
  404.     _doDiagnostics: function()
  405.     {
  406.         try
  407.         {
  408.             var appInfo = this._utils.getAppInfo();
  409.             var date = new Date();
  410.             
  411.             // AFM - SeaMonkey/XPFE
  412.             //
  413.             var osString = "nsIXULRuntime" in CI ?
  414.                 CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULRuntime).OS :
  415.                 CC["@mozilla.org/network/protocol;1?name=http"].getService(CI.nsIHttpProtocolHandler).oscpu;
  416.  
  417.             var localeService = CC["@mozilla.org/intl/nslocaleservice;1"].getService(CI.nsILocaleService);
  418.             var systemLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE");
  419.             var appLocale = this._utils.getAppLocale().string;
  420.             var dataSource = this._zoneManager.dataSource;
  421.     
  422.             var em = CC["@mozilla.org/extensions/manager;1"].getService(CI.nsIExtensionManager);
  423.             var addons = em.getItemList(CI.nsIUpdateItem.TYPE_EXTENSION, { });
  424.              var extensionsString = "";
  425.              
  426.             if ("@mozilla.org/extensions/manager;1" in CC && "nsIExtensionManager" in CI)
  427.             {
  428.                 for (var i = 0; i < addons.length; i++)
  429.                 {
  430.                     var addon = addons[i];
  431.                     extensionsString += addon.name + " (" + addon.version + ")";
  432.                     
  433.                     if (i < addons.length - 1)
  434.                         extensionsString += ", ";
  435.                 }
  436.             }
  437.             else
  438.             {
  439.                 // AFM - SeaMonkeyXPFE
  440.                 //
  441.                 extensionsString += "(unavailable)";
  442.             }
  443.  
  444.             this._logger.log("DIAGNOSTICS: FoxClocks " + this._utils.getFoxClocksVersion() +
  445.                 " (" + dataSource.name + " " + dataSource.version + ") / " + appInfo.appName +
  446.                 " " + appInfo.appVersion + " (" + appLocale + ") on " + osString + " (" + systemLocale +
  447.                 "). Auto-update " + (this._updateManager.nextUpdateDate != null ? "enabled" : "disabled") +
  448.                 ". Universal time " + date.toUTCString() + "; local time " + date.toString() + " - offset " + date.getTimezoneOffset() + " mins"
  449.  
  450.                 , this._logger.INFO);
  451.  
  452.             this._logger.log("DIAGNOSTICS: Installed extensions: " + extensionsString + "\n");
  453.         }
  454.         catch (ex)
  455.         {
  456.             this._logger.log("FoxClocks_Engine::_doDiagnostics(): diagnostics failed: " + ex, this._logger.ERROR);
  457.         }         
  458.  
  459.         this._logger.log("FoxClocks_Engine::_doDiagnostics(): isFirefox15Up " + this._utils.isFirefox15Up); 
  460.     },
  461.             
  462.     // ====================================================================================
  463.     // AFM - nsISupports
  464.     QueryInterface: function(aIID)
  465.     {
  466.         if( aIID.equals(CI.nsISupports) ||
  467.             aIID.equals(CI.nsIClassInfo) ||
  468.             aIID.equals(CI.nsIObserver))
  469.         {
  470.             return this;
  471.         }
  472.         
  473.         throw CR.NS_ERROR_NO_INTERFACE;
  474.     },
  475.   
  476.     // ====================================================================================
  477.     // AFM -  nsIClassInfo  
  478.     getInterfaces: function(aCount)
  479.     {            
  480.         var ifaces = new Array();
  481.         ifaces.push(CI.nsISupports);
  482.         ifaces.push(CI.nsIClassInfo);
  483.         ifaces.push(CI.nsIObserver);
  484.         aCount.value = ifaces.length;
  485.         return ifaces;
  486.     },
  487.   
  488.     // ====================================================================================
  489.     // AFM - nsIClassInfo  
  490.     getHelperForLanguage: function(aLanguage) { return null; },
  491.     get contractID() { return FoxClocks_Engine.contractID; },
  492.     get classID() { return FoxClocks_Engine.classID; },
  493.     get classDescription() { return FoxClocks_Engine.classDescription; },
  494.     get implementationLanguage() { return CI.nsIProgrammingLanguage.JAVASCRIPT; },
  495.     get flags() { return CI.nsIClassInfo.SINGLETON; },
  496.   
  497.     // ====================================================================================
  498.     // AFM - nsIObserver
  499.     observe: function(aSubject, aTopic, aData)
  500.     {
  501.         switch(aTopic)
  502.         {                
  503.             case "xpcom-startup":
  504.                 // this is run very early, right after XPCOM is initialized, but before
  505.                 // user profile information is applied. Register ourselves as an observer
  506.                 // for 'profile-after-change' and 'quit-application'
  507.                 //
  508.                 var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);        
  509.                 obsSvc.addObserver(this, "profile-after-change", false);
  510.                 obsSvc.addObserver(this, "quit-application", false);
  511.                 break;
  512.             
  513.             case "profile-after-change":
  514.                 // This happens after profile has been loaded and user preferences have been read.
  515.                 // startup code here                 
  516.                 this.startup();
  517.                 break;
  518.             
  519.             case "quit-application":
  520.                 this.shutdown();
  521.                 var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
  522.                 obsSvc.removeObserver(this, "profile-after-change");
  523.                 obsSvc.removeObserver(this, "quit-application");     
  524.                 var logService = CC["@stemhaus.com/firefox/foxclocks/logger;1"].getService(CI.nsISupports).wrappedJSObject;
  525.                 logService.log("shutdown: " + gCatContractID);                
  526.                 break;
  527.         
  528.             default:
  529.                 this.onObserve(aSubject, aTopic, aData);
  530.         }
  531.     },
  532.     
  533.     // ====================================================================================
  534.     // AFM - http://www.mozilla.org/scriptable/js-components-status.html
  535.     //
  536.     get wrappedJSObject() { return this; }
  537. };
  538.  
  539. // ====================================================================================
  540. // constructors for objects we want to XPCOMify
  541. //
  542. var gXpComObjects = [FoxClocks_Engine];
  543. var gCatObserverName = FoxClocks_Engine.classDescription; // can be anything
  544. var gCatContractID = FoxClocks_Engine.contractID;
  545.  
  546. // **********
  547. // AFM - generic registration code below. Useful URL: http://www.mozilla.org/projects/xpcom/book/cxc/html/weblock.html
  548. // **********
  549.  
  550. // ====================================================================================
  551. function NSGetModule(compMgr, fileSpec)
  552. {
  553.     gModule._catObserverName = gCatObserverName;
  554.     gModule._catContractId = gCatContractID;
  555.     
  556.     for (var i in gXpComObjects)
  557.         gModule._xpComObjects[i] = new FactoryHolder(gXpComObjects[i]);
  558.         
  559.     return gModule;
  560. }
  561.  
  562. // ====================================================================================
  563. function FactoryHolder(aObj)
  564. {
  565.     this.classID = aObj.classID;
  566.     this.contractID = aObj.contractID;
  567.     this.className  = aObj.classDescription;
  568.     this.factory =
  569.     {
  570.         createInstance: function(aOuter, aIID)
  571.         {
  572.             if (aOuter)
  573.                 throw CR.NS_ERROR_NO_AGGREGATION;
  574.                 
  575.             return (new this.constructor).QueryInterface(aIID);
  576.         }
  577.     };
  578.     
  579.     this.factory.constructor = aObj;
  580. }
  581.  
  582. // ====================================================================================
  583. var gModule =
  584. {
  585.     _xpComObjects: {},
  586.     _catObserverName: null,
  587.     _catContractId: null,
  588.     
  589.     // ====================================================================================
  590.     registerSelf: function(aComponentManager, aFileSpec, aLocation, aType)
  591.     {
  592.         aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
  593.         for (var key in this._xpComObjects)
  594.         {
  595.             var obj = this._xpComObjects[key];
  596.             aComponentManager.registerFactoryLocation(obj.classID, obj.className,
  597.             obj.contractID, aFileSpec, aLocation, aType);
  598.         }
  599.         
  600.         var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
  601.         catman.addCategoryEntry("xpcom-startup", this._catObserverName, "" + this._catContractId, true, true);
  602.         catman.addCategoryEntry("xpcom-shutdown", this._catObserverName, "" + this._catContractId, true, true);
  603.     },
  604.  
  605.     // ====================================================================================
  606.     unregisterSelf: function(aCompMgr, aFileSpec, aLocation)
  607.     {
  608.         var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
  609.         catman.deleteCategoryEntry("xpcom-startup", this._catObserverName, true);
  610.         catman.deleteCategoryEntry("xpcom-shutdown", this._catObserverName, true)
  611.                 
  612.         aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
  613.         for (var key in this._xpComObjects)
  614.         {
  615.             var obj = this._xpComObjects[key];
  616.             aComponentManager.unregisterFactoryLocation(obj.classID, aFileSpec);
  617.         }
  618.     },
  619.  
  620.     // ====================================================================================
  621.     getClassObject: function(aComponentManager, aCID, aIID)
  622.     {
  623.         if (!aIID.equals(CI.nsIFactory))
  624.             throw CR.NS_ERROR_NOT_IMPLEMENTED;
  625.         
  626.         for (var key in this._xpComObjects)
  627.         {
  628.             if (aCID.equals(this._xpComObjects[key].classID))
  629.                 return this._xpComObjects[key].factory;
  630.         }
  631.     
  632.         throw CR.NS_ERROR_NO_INTERFACE;
  633.     },
  634.  
  635.     // ====================================================================================
  636.     canUnload: function(aComponentManager) { return true; }
  637. };